%% -*- coding: utf-8 -*-

%% @doc 値の表とデリミタを管理する環境
%%
-module(templ_env).
-export([store_env/1,restore_env/0,init/1,init/3]).
-export([init_vtable/1,store_vtable/1,restore_vtable/0]).
-export([set_delim/2,get_ldelim/0,get_rdelim/0]).
-export([put_value/3,get_by_key/2,get_by_index/2,remove_value/2]).
-export([is_first/1,is_last/1,nth/1,set_first/1,set_last/1,set_nth/2,unset_first/1,unset_last/1,unset_nth/1]).

-include_lib("../include/templ.hrl").

-define(DEFAULT_ENV, templ_env).


%% @spec store_env(env()) -> undefined|env()
%% @doc 環境をプロセスディクショナリーに格納する
%%
store_env(Env) ->
	put(?DEFAULT_ENV, Env).

%% @spec restore_env() -> env()
%% @doc プロセスディクショナリーから環境を得る
%%
restore_env() ->
	case get(?DEFAULT_ENV) of
		undefined -> #templ_env{};
		E -> E
	end.


%% @spec init(list(), string(), string()) -> env()
%% @doc 環境を作成する
%%
init(VTable, LDlm, RDlm) when is_list(LDlm), is_list(RDlm) ->
	Env = init(VTable),
	Env#templ_env{ldelim = LDlm,rdelim = RDlm};
init(VTable, LDlm, RDlm) ->
	throw({{templ_dict,einval},[VTable, LDlm, RDlm]}).

init(List) when is_list(List) -> 
	VTable = init_vtable(List),
	init(VTable);
init(VTable=?DICT_PAT) ->
	#templ_env{vtable = VTable}.


%% @doc 値テーブルを作成する
%%
init_vtable(List) ->
   % 値はリストにしておいて、
   % ローカル変数で上書き時は要素を追加していく
   dict:from_list(lists:map(fun conv_prop/1, List)).

conv_prop({K,V}) ->
	KC = case is_binary(K) of
			 true  -> binary_to_list(K);
			 false -> K
		 end,
	%VC = case is_binary(V) of
	%		 true  -> binary_to_list(V);
	%		 false -> V
	%	 end,
	{KC,[V]}.


%% @spec store_vtable(vtable()) -> env()|undefined
%% @doc デフォルトの値テーブルを設定する
%%
store_vtable(VTable) when is_list(VTable) ->
	store_vtable(init_vtable(VTable));
store_vtable(VTable) ->
	Env = restore_env(),
	NewEnv = Env#templ_env{vtable = VTable},
	store_env(NewEnv).

%% @spec restore_vtable() -> vtable()
%% @doc デフォルトの値テーブルを得る
%%
restore_vtable() ->
	Env = restore_env(),
	Env#templ_env.vtable.


%% @spec set_delim(delim(), delim()) -> any()
%% @doc (デフォルトの) デリミタを設定する
%%
set_delim(LDelim, RDelim) ->
	Env = restore_env(),
	NewEnv = Env#templ_env{ldelim = LDelim, rdelim = RDelim},
	store_env(NewEnv).

%% @spec get_ldelim() -> delim()
%% @doc (デフォルトの) 開始デリミタを得る
%%
get_ldelim() ->
	Env = restore_env(),
	Env#templ_env.ldelim.

%% @spec get_rdelim() -> delim()
%% @doc (デフォルトの) 終了デリミタを得る
%%
get_rdelim() ->
	Env = restore_env(),
	Env#templ_env.rdelim.


%% @spec put_value(vtable(), key(), value()) -> vtable()
%% @doc 値テーブルにバインディングを追加する
%%
put_value(VTable, Key, Value) ->
	Vals = case dict:find(Key, VTable) of
			   {ok,Vs} -> [Value|Vs];
			   error   -> [Value]
		   end,
	dict:store(Key, Vals, VTable).

%% @spec get_by_key(vtable(), key()) -> vtable()
%% @doc 値を得る
%%
get_by_key(VTable, Key) ->
	case dict:find(Key, VTable) of
		{ok,[V|_Vs]} -> {ok,V};
		Error        -> Error
	end.

%% @spec get_by_index(vtable(), int()) -> vtable()
%% @doc インデックス番号から値を得る
%%
get_by_index(VTable, Idx) ->
	List = dict:to_list(VTable),
	nth_of_list(List, Idx).

nth_of_list([], _) ->
	erlang:error(outofbounds);
nth_of_list([{_,[V|_]}|_], 0) ->
	V;
nth_of_list([{_,_}|R], Idx) when is_integer(Idx) ->
	nth_of_list(R, Idx-1);
nth_of_list(A, Idx) ->
	erlang:error({badargs,[A,Idx]}).


%% @spec remove_value(vtable(), key()) -> vtable()
%% @doc キーの値を削除する
%%
remove_value(VTable, Key) ->
	case dict:fetch(Key, VTable) of
		[_V|[]] -> dict:erase(Key, VTable);
		[_V|Vs] -> dict:store(Key, Vs, VTable)
	end.


%% FIXME: first, lastプロパティ対応

is_first(Var) ->
	case get(first) of
		undefined -> false;
		Vars -> lists:member(Var,Vars)
	end.
is_last(Var) ->
	case get(last) of
		undefined -> false;
		Vars -> lists:member(Var,Vars)
	end.
nth(Var) ->
	case is_first(Var) of
		true -> 0;
		_    ->
			case get(nth) of
				undefined -> none;
				Dict -> 
					case dict:find(Var, Dict) of
						{ok,N} -> N;
						_      -> none
					end
			end
	end.

set_first(Var) ->
	Vars = case get(first) of
			   undefined -> [];
			   Vs -> Vs
	end,
	put(first, [Var|Vars]).
set_last(Var) ->
	Vars = case get(last) of
			   undefined -> [];
			   Vs -> Vs
	end,
	put(last, [Var|Vars]).
set_nth(Var, N) ->
	Dict = case get(nth) of
			   undefined -> dict:new();
			   D -> D
	end,
	put(nth, dict:store(Var, N, Dict)).
	
unset_first(Var) ->
	case get(first) of
		undefined -> none;
		Vars -> put(first,lists:delete(Var, Vars))
	end.
unset_last(Var) ->
	case get(last) of
		undefined -> none;
		Vars -> put(last,lists:delete(Var, Vars))
	end.
unset_nth(Var) ->
	case get(nth) of
		undefined -> none;
		Dict -> 
			try dict:erace(Var, Dict) of
				ND -> put(nth, ND)
			catch
				error:_ -> none
			end
	end.
